﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Caliburn.Micro;
using MIXer.Ch9;
using MIXer.Contracts.Messages;
using MIXer.Contracts.ViewModels;
using MIXer.Core.Contracts;
using MIXer.Core.Domain;
using MIXer.Core.Extensions;
using MIXer.Customization;

namespace MIXer.ViewModels
{
    [Export(typeof(IDownloaderViewModel))]
    public class DownloaderViewModel : Tab, IDownloaderViewModel
    {
        #region Fields
        private EventDelayAggregator _delay4Search;
        private EventDelayAggregator _delay4Location;
        private EventDelayAggregator _delay4Refresh;

        private string _folderLocation;
        private string _nameFormat;
        private string _searchText;
        private string _currSpeed;
        private BindableCollection<DownloadFile> _files;
        private TechEvent _selectedEvent;
        private TechEventDownloadUrl _selectedFeed;

        private ViewableCollection<DownloadFile> _toDownloadFiles;
        private ViewableCollection<DownloadFile> _downloadingFiles;
        private ViewableCollection<DownloadFile> _downloadedFiles;
        #endregion

        #region Constructors
        public DownloaderViewModel()
            : base("Downloader")
        {
            NameFormat = "$code_$title"; // default

            _delay4Search = new EventDelayAggregator(400, () => 
            {
                RefreshAllViews();
            });
            _delay4Location = new EventDelayAggregator(400, () =>
            {
                UpdateFiles(true);
            });
            _delay4Refresh = new EventDelayAggregator(50, () =>
            {
                RefreshAllViews();
            });
        }
        #endregion

        #region Methods
        public void OpenFolderDialog()
        {
            var dialog = new FolderBrowserDialog();
            var result = dialog.ShowDialog();

            if (result == DialogResult.OK)
            {
                FolderLocation = dialog.SelectedPath;
            }
        }
        private void UpdateFiles()
        {
            UpdateFiles(false);
        }
        private void UpdateFiles(bool resetFlags)
        {
            if (Files != null && Files.Count > 0 && !String.IsNullOrEmpty(FolderLocation))
            {
                Parallel.ForEach(Files, f => 
                {
                    if (!f.Downloading && File.Exists(Path.Combine(FolderLocation, NameTransformer.Transform(NameFormat, f.Session) + Path.GetExtension(f.OriginalFileName))))
                    {
                        f.Downloading = false;
                        f.Downloaded = true;
                    }
                    else
                    {
                        f.Downloading = false;
                        f.Downloaded = false;
                    }
                });
            }
            else if (resetFlags && String.IsNullOrEmpty(FolderLocation))
            {
                Parallel.ForEach(Files, f => 
                {
                    f.Downloading = false;
                    f.Downloaded = false;
                });
            }
        }
        private void RefreshAllViews()
        {
            ToDownloadFiles.Refresh();
            DownloadingFiles.Refresh();
            DownloadedFiles.Refresh();
        }
        private void RefreshViews(object sender, PropertyChangedEventArgs args)
        {
            if (args.PropertyName == "Downloading" || args.PropertyName == "Downloaded")
            {
                _delay4Refresh.Signal();
            }
        }
        private async void GetFiles()
        {
            Busy("Getting " + SelectedFeed.Description + " from RSS feed...");

            try
            {
                IList<Session> files = await Ch9RssFeed.GetFilesFromFeed(SelectedFeed.Address, SelectedEvent.EventCode);

                Files = new BindableCollection<DownloadFile>(files.Select(f =>
                {
                    var file = new DownloadFile
                    {
                        Session = f,
                        OriginalFileName = f.VideoUrl.Segments[f.VideoUrl.Segments.Length - 1],
                        IsSelected = false
                    };
                    file.PropertyChanged += RefreshViews;
                    return file;
                }).ToList());

                ToDownloadFiles = new ViewableCollection<DownloadFile>(Files);
                ToDownloadFiles.FilterWhen(f => !f.Downloaded && !f.Downloading && ContainsFilter(f));

                DownloadingFiles = new ViewableCollection<DownloadFile>(Files);
                DownloadingFiles.FilterWhen(f => !f.Downloaded && f.Downloading && ContainsFilter(f));

                DownloadedFiles = new ViewableCollection<DownloadFile>(Files);
                DownloadedFiles.FilterWhen(f => f.Downloaded && !f.Downloading && ContainsFilter(f));

                UpdateFiles(false);
            }
            catch (WebException exception)
            {
                MessageBox.Show(exception.Message, "Network error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message, "Unexpected error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            Free();
        }
        public async void RefreshFiles()
        {
            Busy("Refreshing " + SelectedFeed.Description + " from RSS feed...");

            try
            {
                IList<Session> files = await Ch9RssFeed.GetFilesFromFeed(SelectedFeed.Address, SelectedEvent.EventCode);

                foreach (var f in files)
                {
                    if (!Files.Any(d => d.Session.EventCode == f.EventCode && d.Session.SessionCode == f.SessionCode))
                    {
                        var file = new DownloadFile
                        {
                            Session = f,
                            OriginalFileName = f.VideoUrl.Segments[f.VideoUrl.Segments.Length - 1],
                            IsSelected = false
                        };
                        file.PropertyChanged += RefreshViews;
                        Files.Add(file);
                    }
                }
            }
            catch (WebException exception)
            {
                MessageBox.Show(exception.Message, "Network error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message, "Unexpected error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            Free();
        }
        public void FileCheckChanged(DownloadFile file)
        {
            if (file != null)
            {
                FilesContentChanged();
            }
        }
        public void SelectAll()
        {
            foreach (var file in Files)
            {
                if(!file.Downloaded || !file.Downloading)
                {
                    file.IsSelected = true;
                }
            }

            FilesContentChanged();
        }
        public void DeSelectAll()
        {
            foreach (var file in Files)
            {
                if (!file.Downloaded || !file.Downloading)
                {
                    file.IsSelected = false;
                }
            }

            FilesContentChanged();
        }
        private IWebProxy GetProxy()
        {
            var useProxy = ConfigurationProvider.GetValueAs<bool>("UseProxy");
            var useDefault = ConfigurationProvider.GetValueAs<bool>("UseDefaultProxy");

            if (!useProxy)
            {
                return null;
            }

            if (useDefault)
            {
                return WebRequest.GetSystemWebProxy();
            }

            var proxy = new WebProxy(ConfigurationProvider.GetValueAs<string>("ProxyServer"), ConfigurationProvider.GetValueAs<int>("ProxyPort"));
            proxy.BypassProxyOnLocal = true;

            var username = ConfigurationProvider.GetValueAs<string>("ProxyUser");
            string domain = null;

            if (!String.IsNullOrEmpty(username) && username.Contains('\\'))
            {
                var indexOfBackSlash = username.IndexOf('\\');
                domain = username.Substring(0, indexOfBackSlash);
                username = username.Substring(indexOfBackSlash + 1);
            }

            if (!String.IsNullOrEmpty(username))
            {
                if (domain != null)
                {
                    proxy.Credentials = new NetworkCredential(username, PasswordHelper.Decrypt(ConfigurationProvider.GetValueAs<string>("ProxyPassword")), domain);
                }
                else
                {
                    proxy.Credentials = new NetworkCredential(username, PasswordHelper.Decrypt(ConfigurationProvider.GetValueAs<string>("ProxyPassword")));
                }
            }

            return proxy;
        }
        public void DownloadFiles()
        {
            if (!Directory.Exists(FolderLocation))
            {
                var answer = MessageBox.Show("The selected folder does not exist. Do you wish to create it?", "Create folder?", MessageBoxButtons.YesNo, MessageBoxIcon.Question);

                if (answer == DialogResult.No)
                {
                    return;
                }

                Directory.CreateDirectory(FolderLocation);
            }

            var selectedFiles = (from f in Files
                                 where f.IsSelected == true && f.Downloaded == false && f.Downloading == false && f.Progress == 0
                                 select f).ToList();

            Parallel.ForEach(selectedFiles, f => f.StartDownloading(Path.Combine(FolderLocation, NameTransformer.Transform(NameFormat, f.Session) + Path.GetExtension(f.OriginalFileName)), GetProxy(), () => 
            { 
                // called when some progress changed
                var query = from file in Files
                            where file.Downloading == true
                            group file by file.Downloading into g
                            select new
                            {
                                CountDownloading = g.Count(),
                                TotalProgress = g.Sum(x => x.Progress),
                                TotalSpeed = g.Sum(x => x.Speed)
                            };

                var result = query.FirstOrDefault();

                if (result != null)
                {
                    if (result.CountDownloading > 0)
                    {
                        CurrentProgress = result.TotalProgress / result.CountDownloading;
                        CurrentSpeed = result.TotalSpeed.ToFriendlySpeed();
                        InProgress = true;
                    }
                    else
                    {
                        InProgress = false;
                        CurrentProgress = 0;
                        CurrentSpeed = String.Empty;
                    }
                }
                else
                {
                    InProgress = false;
                    CurrentProgress = 0;
                    CurrentSpeed = String.Empty;
                }

                EventAggregator.Publish(new TabRefreshed { Tab = this });
            }));
        }
        public void Cancel(DownloadFile file)
        {
            file.Cancel();
        }
        private void FilesContentChanged()
        {
            NotifyOfPropertyChange(() => CanSelectAll);
            NotifyOfPropertyChange(() => CanDeSelectAll);
            NotifyOfPropertyChange(() => CanDownloadFiles);
        }
        public void SearchTextChanged(string text)
        {
            _delay4Search.Signal();
        }
        private bool ContainsFilter(DownloadFile file)
        {
            if (String.IsNullOrEmpty(this.SearchText))
                return true;

            var textToSearch = this.SearchText.ToUpperInvariant();
            
            if (file.Session.EventCode.ToUpperInvariant().Contains(textToSearch) 
                || file.Session.Title.ToUpperInvariant().Contains(textToSearch) 
                || file.Session.Author.ToUpperInvariant().Contains(textToSearch))
            {
                return true;
            }

            return false;
        }
        #endregion

        #region Properties
        public TechEvent SelectedEvent
        {
            get { return _selectedEvent; }
            set
            {
                _selectedEvent = value;
                NotifyOfPropertyChange(() => SelectedEvent);

                SelectedFeed = null;
                NotifyOfPropertyChange(() => Feeds);
            }
        }
        public TechEventDownloadUrl SelectedFeed
        {
            get { return _selectedFeed; }
            set
            {
                _selectedFeed = value;

                NotifyOfPropertyChange(() => SelectedFeed);
                NotifyOfPropertyChange(() => CanDownloadFiles);

                if (_selectedFeed != null)
                {
                    GetFiles();
                }
                else
                {
                    Files = new BindableCollection<DownloadFile>();
                }
            }
        }
        public string FolderLocation
        {
            get { return _folderLocation; }
            set
            {
                _folderLocation = value;

                _delay4Location.Signal();

                NotifyOfPropertyChange(() => FolderLocation);
                NotifyOfPropertyChange(() => CanDownloadFiles);
            }
        }
        public string NameFormat
        {
            get { return _nameFormat; }
            set
            {
                _nameFormat = value;

                NotifyOfPropertyChange(() => NameFormat);
                NotifyOfPropertyChange(() => CanDownloadFiles);
            }
        }
        public string SearchText
        {
            get { return _searchText; }
            set
            {
                _searchText = value;

                NotifyOfPropertyChange(() => SearchText);
            }
        }
        public string CurrentSpeed
        {
            get { return _currSpeed; }
            set
            {
                _currSpeed = value;

                NotifyOfPropertyChange(() => CurrentSpeed);
            }
        }
        public BindableCollection<DownloadFile> Files
        {
            get { return _files; }
            set
            {
                _files = value;

                NotifyOfPropertyChange(() => Files);
                FilesContentChanged();
            }
        }
        public ViewableCollection<DownloadFile> ToDownloadFiles
        {
            get { return _toDownloadFiles; }
            set
            {
                _toDownloadFiles = value;

                NotifyOfPropertyChange(() => ToDownloadFiles);                
            }
        }
        public ViewableCollection<DownloadFile> DownloadingFiles
        {
            get { return _downloadingFiles; }
            set
            {
                _downloadingFiles = value;

                NotifyOfPropertyChange(() => DownloadingFiles);
            }
        }
        public ViewableCollection<DownloadFile> DownloadedFiles
        {
            get { return _downloadedFiles; }
            set
            {
                _downloadedFiles = value;

                NotifyOfPropertyChange(() => DownloadedFiles);
            }
        }
        public BindableCollection<TechEventDownloadUrl> Feeds
        {
            get
            {
                if (SelectedEvent == null)
                    return new BindableCollection<TechEventDownloadUrl>();

                return new BindableCollection<TechEventDownloadUrl>(SelectedEvent.Links);
            }
        }
        public BindableCollection<TechEvent> Events
        {
            get { return new BindableCollection<TechEvent>(AvailableEventsProvider.AvailableEvents); }
        }
        public string AvailableTokens
        {
            get
            {
                StringBuilder tokens = new StringBuilder();
                foreach (var token in NameTransformer.AvailableTokens)
                {
                    if (tokens.Length > 0) tokens.Append(", ");
                    tokens.Append(token);
                }

                return tokens.ToString();
            }
        }

        #region Conditional Properties
        public bool CanSelectAll
        {
            get { return _files != null && _files.Any(f => !f.IsSelected); }
        }
        public bool CanDeSelectAll
        {
            get { return _files != null && _files.Any(f => f.IsSelected); }
        }
        public bool CanDownloadFiles
        {
            get { return _files != null && _files.Any(f => f.IsSelected) && SelectedEvent != null && SelectedFeed != null && !String.IsNullOrEmpty(NameFormat) && !String.IsNullOrEmpty(FolderLocation); }
        }
        #endregion

        #region Providers
        [Import(typeof(IAvailableEventsProvider), AllowDefault = true)]
        public IAvailableEventsProvider AvailableEventsProvider { get; set; }

        [Import(typeof(INameTransformer), AllowDefault = true)]
        public INameTransformer NameTransformer { get; set; }

        [Import(typeof(IConfigurationProvider), AllowDefault = true)]
        public IConfigurationProvider ConfigurationProvider { get; set; }
        #endregion

        #region Events
        [Import(typeof(IEventAggregator), AllowDefault = true)]
        public IEventAggregator EventAggregator { get; set; }
        #endregion

        #endregion
    }
}
